/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "gamePCH.h"
#include "DatabaseEnv.h"
#include "SQLStorage.h"
#include "ObjectMgr.h"
#include "ObjectDefines.h"
#include "GridDefines.h"
#include "GridNotifiers.h"
#include "SpellMgr.h"
#include "GridNotifiersImpl.h"
#include "Cell.h"
#include "CellImpl.h"
#include "InstanceScript.h"
#include "ScriptedCreature.h"

#include "SmartScriptMgr.h"

void SmartWaypointMgr::LoadFromDB() {
	uint32 oldMSTime = getMSTime();

	waypoint_map.clear();

	PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(
			WORLD_LOAD_SMARTAI_WP);
	PreparedQueryResult result = WorldDatabase.Query(stmt);

	if (!result) {
		sLog->outString(
				">> Loaded 0 SmartAI Waypoint Paths. DB table `waypoints` is empty.");
		sLog->outString();
		return;
	}

	uint32 count = 0;
	uint32 total = 0;
	WPPath* path = NULL;
	uint32 last_entry = 0;
	uint32 last_id = 1;

	do {
		Field* fields = result->Fetch();
		uint32 entry = fields[0].GetUInt32();
		uint32 id = fields[1].GetUInt32();
		float x, y, z;
		x = fields[2].GetFloat();
		y = fields[3].GetFloat();
		z = fields[4].GetFloat();

		WayPoint* wp = new WayPoint(id, x, y, z);

		if (last_entry != entry) {
			path = new WPPath;
			last_id = 1;
		}
		if (last_id != id) {
			sLog->outErrorDb(
					"SmartWaypointMgr::LoadFromDB: Path entry %u, unexpected point id %u, expected %u.",
					entry, id, last_id);
		}
		last_id++;
		(*path)[id] = wp;

		if (last_entry != entry) {
			count++;
			waypoint_map[entry] = path;
		}
		last_entry = entry;
		total++;
	} while (result->NextRow());

	sLog->outString(
			">> Loaded %u SmartAI waypoint paths (total %u waypoints) in %u ms",
			count, total, GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

void SmartAIMgr::LoadSmartAIFromDB() {
	uint32 oldMSTime = getMSTime();

	for (uint8 i = 0; i < SMART_SCRIPT_TYPE_MAX; i++)
		mEventMap[i].clear(); //Drop Existing SmartAI List

	PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(
			WORLD_LOAD_SMART_SCRIPTS);
	PreparedQueryResult result = WorldDatabase.Query(stmt);

	if (!result) {
		sLog->outString(
				">> Loaded 0 SmartAI scripts. DB table `smartai_scripts` is empty.");
		sLog->outString();
		return;
	}

	uint32 count = 0;

	do {
		Field* fields = result->Fetch();

		SmartScriptHolder temp;

		temp.entryOrGuid = fields[0].GetInt32();
		SmartScriptType source_type = (SmartScriptType) fields[1].GetUInt8();
		if (source_type >= SMART_SCRIPT_TYPE_MAX) {
			sLog->outErrorDb(
					"SmartAIMgr::LoadSmartAIFromDB: invalid source_type (%u), skipped loading.",
					uint32(source_type));
			continue;
		}
		if (temp.entryOrGuid >= 0) {
			switch (source_type) {
			case SMART_SCRIPT_TYPE_CREATURE: {
				if (!sCreatureStorage.LookupEntry<CreatureInfo>(
						(uint32) temp.entryOrGuid)) {
					sLog->outErrorDb(
							"SmartAIMgr::LoadSmartAIFromDB: Creature entry (%u) does not exist, skipped loading.",
							uint32(temp.entryOrGuid));
					continue;
				}
				break;
			}
			case SMART_SCRIPT_TYPE_GAMEOBJECT: {
				if (!sGOStorage.LookupEntry<GameObjectInfo>(
						(uint32) temp.entryOrGuid)) {
					sLog->outErrorDb(
							"SmartAIMgr::LoadSmartAIFromDB: GameObject entry (%u) does not exist, skipped loading.",
							uint32(temp.entryOrGuid));
					continue;
				}
				break;
			}
			case SMART_SCRIPT_TYPE_AREATRIGGER: {
				if (!sAreaTriggerStore.LookupEntry((uint32) temp.entryOrGuid)) {
					sLog->outErrorDb(
							"SmartAIMgr::LoadSmartAIFromDB: AreaTrigger entry (%u) does not exist, skipped loading.",
							uint32(temp.entryOrGuid));
					continue;
				}
				break;
			}
			case SMART_SCRIPT_TYPE_TIMED_ACTIONLIST:
				break; //nothing to check, really
			default:
				sLog->outErrorDb(
						"SmartAIMgr::LoadSmartAIFromDB: not yet implemented source_type %u",
						(uint32) source_type);
				continue;
			}
		} else {
			if (!sObjectMgr->GetCreatureData(uint32(abs(temp.entryOrGuid)))) {
				sLog->outErrorDb(
						"SmartAIMgr::LoadSmartAIFromDB: Creature guid (%u) does not exist, skipped loading.",
						uint32(abs(temp.entryOrGuid)));
				continue;
			}
		}
		temp.source_type = source_type;
		temp.event_id = fields[2].GetUInt16();
		temp.link = fields[3].GetUInt16();
		temp.event.type = (SMART_EVENT) fields[4].GetUInt8();
		temp.event.event_phase_mask = fields[5].GetUInt8();
		temp.event.event_chance = fields[6].GetUInt8();
		temp.event.event_flags = fields[7].GetUInt8();

		temp.event.raw.param1 = fields[8].GetUInt32();
		temp.event.raw.param2 = fields[9].GetUInt32();
		temp.event.raw.param3 = fields[10].GetUInt32();
		temp.event.raw.param4 = fields[11].GetUInt32();

		temp.action.type = (SMART_ACTION) fields[12].GetUInt8();

		temp.action.raw.param1 = fields[13].GetUInt32();
		temp.action.raw.param2 = fields[14].GetUInt32();
		temp.action.raw.param3 = fields[15].GetUInt32();
		temp.action.raw.param4 = fields[16].GetUInt32();
		temp.action.raw.param5 = fields[17].GetUInt32();
		temp.action.raw.param6 = fields[18].GetUInt32();

		temp.target.type = (SMARTAI_TARGETS) fields[19].GetUInt8();
		temp.target.raw.param1 = fields[20].GetUInt32();
		temp.target.raw.param2 = fields[21].GetUInt32();
		temp.target.raw.param3 = fields[22].GetUInt32();
		temp.target.x = fields[23].GetFloat();
		temp.target.y = fields[24].GetFloat();
		temp.target.z = fields[25].GetFloat();
		temp.target.o = fields[26].GetFloat();

		//check target
		if (!IsTargetValid(temp))
			continue;

		// check all event and action params
		if (!IsEventValid(temp))
			continue;

		// creature entry / guid not found in storage, create empty event list for it and increase counters
		if (mEventMap[source_type].find(temp.entryOrGuid)
				== mEventMap[source_type].end()) {
			++count;
			SmartAIEventList eventList;
			mEventMap[source_type][temp.entryOrGuid] = eventList;
		}
		// store the new event
		mEventMap[source_type][temp.entryOrGuid].push_back(temp);
	} while (result->NextRow());

	sLog->outString(">> Loaded %u SmartAI scripts in %u ms", count,
			GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

bool SmartAIMgr::IsTargetValid(SmartScriptHolder e) {
	if (e.GetActionType() == SMART_ACTION_INSTALL_AI_TEMPLATE)
		return true; //AI template has special handling
	switch (e.GetTargetType()) {
	case SMART_TARGET_CREATURE_DISTANCE:
	case SMART_TARGET_CREATURE_RANGE: {
		if (e.target.unitDistance.creature
				&& !sCreatureStorage.LookupEntry<CreatureInfo>(
						e.target.unitDistance.creature)) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Creature entry %u as target_param1, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.target.unitDistance.creature);
			return false;
		}
		break;
	}
	case SMART_TARGET_GAMEOBJECT_DISTANCE:
	case SMART_TARGET_GAMEOBJECT_RANGE: {
		if (e.target.goDistance.entry
				&& !sGOStorage.LookupEntry<GameObjectInfo>(
						e.target.goDistance.entry)) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent GameObject entry %u as target_param1, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.target.goDistance.entry);
			return false;
		}
		break;
	}
	case SMART_TARGET_CREATURE_GUID: {
		if (e.target.unitGUID.entry
				&& !IsCreatureValid(e, e.target.unitGUID.entry))
			return false;
		break;
	}
	case SMART_TARGET_GAMEOBJECT_GUID: {
		if (e.target.goGUID.entry
				&& !IsGameObjectValid(e, e.target.goGUID.entry))
			return false;
		break;
	}
	case SMART_TARGET_PLAYER_RANGE:
	case SMART_TARGET_PLAYER_DISTANCE:
	case SMART_TARGET_SELF:
	case SMART_TARGET_VICTIM:
	case SMART_TARGET_HOSTILE_SECOND_AGGRO:
	case SMART_TARGET_HOSTILE_LAST_AGGRO:
	case SMART_TARGET_HOSTILE_RANDOM:
	case SMART_TARGET_HOSTILE_RANDOM_NOT_TOP:
	case SMART_TARGET_ACTION_INVOKER:
	case SMART_TARGET_POSITION:
	case SMART_TARGET_NONE:
	case SMART_TARGET_CLOSEST_CREATURE:
	case SMART_TARGET_CLOSEST_GAMEOBJECT:
	case SMART_TARGET_CLOSEST_PLAYER:
	case SMART_TARGET_ACTION_INVOKER_VEHICLE:
	case SMART_TARGET_OWNER_OR_SUMMONER:
	case SMART_TARGET_THREAT_LIST:
		break;
	default:
		sLog->outErrorDb(
				"SmartAIMgr: Not handled target_type(%u), Entry %d SourceType %u Event %u Action %u, skipped.",
				e.GetTargetType(), e.entryOrGuid, e.GetScriptType(), e.event_id,
				e.GetActionType());
		return false;
	}
	return true;
}

bool SmartAIMgr::IsEventValid(SmartScriptHolder &e) {
	if (e.event.type >= SMART_EVENT_END) {
		sLog->outErrorDb(
				"SmartAIMgr: EntryOrGuid %d using event(%u) has invalid event type (%u), skipped.",
				e.entryOrGuid, e.event_id, e.GetEventType());
		return false;
	}
	// in SMART_SCRIPT_TYPE_TIMED_ACTIONLIST all event types are overriden by core
	if (e.GetScriptType() != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST
			&& !(SmartAIEventMask[e.event.type][1]
					& SmartAITypeMask[e.GetScriptType()][1])) {
		sLog->outErrorDb(
				"SmartAIMgr: EntryOrGuid %d, event type %u can not be used for Script type %u",
				e.entryOrGuid, e.GetEventType(), e.GetScriptType());
		return false;
	}
	if (e.action.type >= SMART_ACTION_END) {
		sLog->outErrorDb(
				"SmartAIMgr: EntryOrGuid %d using event(%u) has invalid action type (%u), skipped.",
				e.entryOrGuid, e.event_id, e.GetActionType());
		return false;
	}
	if (e.event.event_phase_mask > SMART_EVENT_PHASE_ALL) {
		sLog->outErrorDb(
				"SmartAIMgr: EntryOrGuid %d using event(%u) has invalid phase mask (%u), skipped.",
				e.entryOrGuid, e.event_id, e.event.event_phase_mask);
		return false;
	}
	if (e.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) {
		e.event.type = SMART_EVENT_UPDATE_OOC; //force default OOC, can change when calling the script!
		if (!IsMinMaxValid(e, e.event.minMaxRepeat.min,
				e.event.minMaxRepeat.max))
			return false;
		if (!IsMinMaxValid(e, e.event.minMaxRepeat.repeatMin,
				e.event.minMaxRepeat.repeatMax))
			return false;
	} else {
		uint32 type = e.event.type;
		switch (type) {
		case SMART_EVENT_UPDATE:
		case SMART_EVENT_UPDATE_IC:
		case SMART_EVENT_UPDATE_OOC:
		case SMART_EVENT_HEALT_PCT:
		case SMART_EVENT_MANA_PCT:
		case SMART_EVENT_TARGET_HEALTH_PCT:
		case SMART_EVENT_TARGET_MANA_PCT:
		case SMART_EVENT_RANGE:
		case SMART_EVENT_DAMAGED:
		case SMART_EVENT_DAMAGED_TARGET:
		case SMART_EVENT_RECEIVE_HEAL:
			if (!IsMinMaxValid(e, e.event.minMaxRepeat.min,
					e.event.minMaxRepeat.max))
				return false;
			if (!IsMinMaxValid(e, e.event.minMaxRepeat.repeatMin,
					e.event.minMaxRepeat.repeatMax))
				return false;
			break;
		case SMART_EVENT_SPELLHIT:
		case SMART_EVENT_SPELLHIT_TARGET:
			if (e.event.spellHit.spell) {
				SpellEntry const* pSpell = sSpellStore.LookupEntry(
						e.event.spellHit.spell);
				if (!pSpell) {
					sLog->outErrorDb(
							"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Spell entry %u, skipped.",
							e.entryOrGuid, e.GetScriptType(), e.event_id,
							e.GetActionType(), e.event.spellHit.spell);
					return false;
				}
				if (e.event.spellHit.school
						&& (e.event.spellHit.school & pSpell->SchoolMask)
								!= pSpell->SchoolMask) {
					sLog->outErrorDb(
							"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses Spell entry %u with invalid school mask, skipped.",
							e.entryOrGuid, e.GetScriptType(), e.event_id,
							e.GetActionType(), e.event.spellHit.spell);
					return false;
				}
			}
			if (!IsMinMaxValid(e, e.event.spellHit.cooldownMin,
					e.event.spellHit.cooldownMax))
				return false;
			break;
		case SMART_EVENT_OOC_LOS:
		case SMART_EVENT_IC_LOS:
			if (!IsMinMaxValid(e, e.event.los.cooldownMin,
					e.event.los.cooldownMax))
				return false;
			break;
		case SMART_EVENT_RESPAWN:
			if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_MAP
					&& !sMapStore.LookupEntry(e.event.respawn.map)) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Map entry %u, skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id,
						e.GetActionType(), e.event.respawn.map);
				return false;
			}
			if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_AREA
					&& !GetAreaEntryByAreaID(e.event.respawn.area)) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Area entry %u, skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id,
						e.GetActionType(), e.event.respawn.area);
				return false;
			}
			break;
		case SMART_EVENT_FRIENDLY_HEALTH:
			if (!NotNULL(e, e.event.friendlyHealt.radius))
				return false;
			if (!IsMinMaxValid(e, e.event.friendlyHealt.repeatMin,
					e.event.friendlyHealt.repeatMax))
				return false;
			break;
		case SMART_EVENT_FRIENDLY_IS_CC:
			if (!IsMinMaxValid(e, e.event.friendlyCC.repeatMin,
					e.event.friendlyCC.repeatMax))
				return false;
			break;
		case SMART_EVENT_FRIENDLY_MISSING_BUFF: {
			if (!IsSpellValid(e, e.event.missingBuff.spell))
				return false;
			if (!NotNULL(e, e.event.missingBuff.radius))
				return false;
			if (!IsMinMaxValid(e, e.event.missingBuff.repeatMin,
					e.event.missingBuff.repeatMax))
				return false;
			break;
		}
		case SMART_EVENT_KILL:
			if (!IsMinMaxValid(e, e.event.kill.cooldownMin,
					e.event.kill.cooldownMax))
				return false;
			if (e.event.kill.creature
					&& !IsCreatureValid(e, e.event.kill.creature))
				return false;
			break;
		case SMART_EVENT_TARGET_CASTING:
		case SMART_EVENT_PASSENGER_BOARDED:
		case SMART_EVENT_PASSENGER_REMOVED:
			if (!IsMinMaxValid(e, e.event.minMax.repeatMin,
					e.event.minMax.repeatMax))
				return false;
			break;
		case SMART_EVENT_SUMMON_DESPAWNED:
		case SMART_EVENT_SUMMONED_UNIT:
			if (e.event.summoned.creature
					&& !IsCreatureValid(e, e.event.summoned.creature))
				return false;
			if (!IsMinMaxValid(e, e.event.summoned.cooldownMin,
					e.event.summoned.cooldownMax))
				return false;
			break;
		case SMART_EVENT_ACCEPTED_QUEST:
		case SMART_EVENT_REWARD_QUEST:
			if (!IsQuestValid(e, e.event.quest.quest))
				return false;
			break;
		case SMART_EVENT_RECEIVE_EMOTE: {
			if (e.event.emote.emote
					&& !IsTextEmoteValid(e, e.event.emote.emote))
				return false;
			if (!IsMinMaxValid(e, e.event.emote.cooldownMin,
					e.event.emote.cooldownMax))
				return false;
			break;
		}
		case SMART_EVENT_HAS_AURA:
		case SMART_EVENT_TARGET_BUFFED: {
			if (!IsSpellValid(e, e.event.aura.spell))
				return false;
			if (!IsMinMaxValid(e, e.event.aura.repeatMin,
					e.event.aura.repeatMax))
				return false;
			break;
		}
		case SMART_EVENT_TRANSPORT_ADDCREATURE: {
			if (e.event.transportAddCreature.creature
					&& !IsCreatureValid(e,
							e.event.transportAddCreature.creature))
				return false;
			break;
		}
		case SMART_EVENT_MOVEMENTINFORM: {
			if (e.event.movementInform.type > NULL_MOTION_TYPE) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses invalid Motion type %u, skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id,
						e.GetActionType(), e.event.movementInform.type);
				return false;
			}
			break;
		}
		case SMART_EVENT_DATA_SET: {
			if (!IsMinMaxValid(e, e.event.dataSet.cooldownMin,
					e.event.dataSet.cooldownMax))
				return false;
			break;
		}
		case SMART_EVENT_AREATRIGGER_ONTRIGGER: {
			if (e.event.areatrigger.id
					&& !IsAreaTriggerValid(e, e.event.areatrigger.id))
				return false;
			break;
		}
		case SMART_EVENT_TEXT_OVER:
			//if (e.event.textOver.textGroupID && !IsTextValid(e, e.event.textOver.textGroupID)) return false;// 0 is a valid text group!
			break;
		case SMART_EVENT_LINK: {
			if (e.link && e.link == e.event_id) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u, Event %u, Link Event is linking self (infinite loop), skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id);
				return false;
			}
			break;
		}
		case SMART_EVENT_DUMMY_EFFECT:
			if (!IsSpellValid(e, e.event.dummy.spell))
				return false;
			if (e.event.dummy.effIndex > EFFECT_2)
				return false;
			break;
		case SMART_EVENT_IS_BEHIND_TARGET:
			if (!IsMinMaxValid(e, e.event.behindTarget.cooldownMin,
					e.event.behindTarget.cooldownMax))
				return false;
			break;
		case SMART_EVENT_TIMED_EVENT_TRIGGERED:
		case SMART_EVENT_INSTANCE_PLAYER_ENTER:
		case SMART_EVENT_TRANSPORT_RELOCATE:
		case SMART_EVENT_CHARMED:
		case SMART_EVENT_CHARMED_TARGET:
		case SMART_EVENT_CORPSE_REMOVED:
		case SMART_EVENT_AI_INIT:
		case SMART_EVENT_TRANSPORT_ADDPLAYER:
		case SMART_EVENT_TRANSPORT_REMOVE_PLAYER:
		case SMART_EVENT_AGGRO:
		case SMART_EVENT_DEATH:
		case SMART_EVENT_EVADE:
		case SMART_EVENT_REACHED_HOME:
		case SMART_EVENT_RESET:
		case SMART_EVENT_QUEST_ACCEPTED:
		case SMART_EVENT_QUEST_OBJ_COPLETETION:
		case SMART_EVENT_QUEST_COMPLETION:
		case SMART_EVENT_QUEST_REWARDED:
		case SMART_EVENT_QUEST_FAIL:
		case SMART_EVENT_JUST_SUMMONED:
		case SMART_EVENT_WAYPOINT_START:
		case SMART_EVENT_WAYPOINT_REACHED:
		case SMART_EVENT_WAYPOINT_PAUSED:
		case SMART_EVENT_WAYPOINT_RESUMED:
		case SMART_EVENT_WAYPOINT_STOPPED:
		case SMART_EVENT_WAYPOINT_ENDED:
		case SMART_ACTION_PLAYMOVIE:
		case SMART_EVENT_GOSSIP_SELECT:
		case SMART_EVENT_GOSSIP_HELLO:
		case SMART_EVENT_JUST_CREATED:
		case SMART_EVENT_FOLLOW_COMPLETED:
			break;
		default:
			sLog->outErrorDb(
					"SmartAIMgr: Not handled event_type(%u), Entry %d SourceType %u Event %u Action %u, skipped.",
					e.GetEventType(), e.entryOrGuid, e.GetScriptType(),
					e.event_id, e.GetActionType());
			return false;
		}
	}

	switch (e.GetActionType()) {
	case SMART_ACTION_SET_FACTION:
		if (e.action.faction.factionID
				&& !sFactionStore.LookupEntry(e.action.faction.factionID)) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Faction %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.faction.factionID);
			return false;
		}
		break;
	case SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL:
	case SMART_ACTION_MOUNT_TO_ENTRY_OR_MODEL:
		if (e.action.morphOrMount.creature || e.action.morphOrMount.model) {
			if (e.action.morphOrMount.creature > 0
					&& !sCreatureStorage.LookupEntry<CreatureInfo>(
							e.action.morphOrMount.creature)) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Creature entry %u, skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id,
						e.GetActionType(), e.action.morphOrMount.creature);
				return false;
			}

			if (e.action.morphOrMount.model) {
				if (e.action.morphOrMount.creature) {
					sLog->outErrorDb(
							"SmartAIMgr: Entry %d SourceType %u Event %u Action %u has ModelID set with also set CreatureId, skipped.",
							e.entryOrGuid, e.GetScriptType(), e.event_id,
							e.GetActionType());
					return false;
				} else if (!sCreatureDisplayInfoStore.LookupEntry(
						e.action.morphOrMount.model)) {
					sLog->outErrorDb(
							"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Model id %u, skipped.",
							e.entryOrGuid, e.GetScriptType(), e.event_id,
							e.GetActionType(), e.action.morphOrMount.model);
					return false;
				}
			}
		}
		break;
	case SMART_ACTION_SOUND:
		if (!IsSoundValid(e, e.action.sound.sound))
			return false;
		if (e.action.sound.range > TEXT_RANGE_WORLD) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses invalid Text Range %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.sound.range);
			return false;
		}
		break;
	case SMART_ACTION_SET_EMOTE_STATE:
	case SMART_ACTION_PLAY_EMOTE:
		if (!IsEmoteValid(e, e.action.emote.emote))
			return false;
		break;
	case SMART_ACTION_FAIL_QUEST:
	case SMART_ACTION_ADD_QUEST:
		if (e.action.quest.quest && !IsQuestValid(e, e.action.quest.quest))
			return false;
		break;
	case SMART_ACTION_ACTIVATE_TAXI: {
		if (!sTaxiPathStore.LookupEntry(e.action.taxi.id)) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses invalid Taxi path ID %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.taxi.id);
			return false;
		}
		break;
	}
	case SMART_ACTION_RANDOM_EMOTE:
		if (e.action.randomEmote.emote1
				&& !IsEmoteValid(e, e.action.randomEmote.emote1))
			return false;
		if (e.action.randomEmote.emote2
				&& !IsEmoteValid(e, e.action.randomEmote.emote2))
			return false;
		if (e.action.randomEmote.emote3
				&& !IsEmoteValid(e, e.action.randomEmote.emote3))
			return false;
		if (e.action.randomEmote.emote4
				&& !IsEmoteValid(e, e.action.randomEmote.emote4))
			return false;
		if (e.action.randomEmote.emote5
				&& !IsEmoteValid(e, e.action.randomEmote.emote5))
			return false;
		if (e.action.randomEmote.emote6
				&& !IsEmoteValid(e, e.action.randomEmote.emote6))
			return false;
		break;
	case SMART_ACTION_ADD_AURA:
	case SMART_ACTION_CAST:
	case SMART_ACTION_INVOKER_CAST:
		if (!IsSpellValid(e, e.action.cast.spell))
			return false;
		break;
	case SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS:
	case SMART_ACTION_CALL_GROUPEVENTHAPPENS:
		if (Quest const* qid = sObjectMgr->GetQuestTemplate(e.action.quest.quest)) {
			if (!qid->HasSpecialFlag(QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT)) {
				sLog->outErrorDb(
						"SmartAIMgr: Entry %d SourceType %u Event %u Action %u SpecialFlags for Quest entry %u does not include FLAGS_EXPLORATION_OR_EVENT(2), skipped.",
						e.entryOrGuid, e.GetScriptType(), e.event_id,
						e.GetActionType(), e.action.quest.quest);
				return false;
			}
		} else {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Quest entry %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.quest.quest);
			return false;
		}
		break;
	case SMART_ACTION_SEND_CASTCREATUREORGO:
		if (!IsQuestValid(e, e.action.castCreatureOrGO.quest))
			return false;
		if (!IsSpellValid(e, e.action.castCreatureOrGO.spell))
			return false;
		break;

	case SMART_ACTION_SET_EVENT_PHASE:
		if (e.action.setEventPhase.phase >= SMART_EVENT_PHASE_MAX) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u attempts to set phase %u. Phase mask cannot be used past phase %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.setEventPhase.phase,
					SMART_EVENT_PHASE_MAX - 1);
			return false;
		}
		break;
	case SMART_ACTION_INC_EVENT_PHASE:
		if (!e.action.incEventPhase.inc && !e.action.incEventPhase.dec) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u is incrementing phase by 0, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType());
			return false;
		} else if (e.action.incEventPhase.inc > SMART_EVENT_PHASE_MAX
				|| e.action.incEventPhase.dec > SMART_EVENT_PHASE_MAX) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u attempts to increment phase by too large value, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType());
			return false;
		}
		break;
	case SMART_ACTION_CALL_CASTEDCREATUREORGO:
		if (!IsCreatureValid(e, e.action.castedCreatureOrGO.creature))
			return false;
		if (!IsSpellValid(e, e.action.castedCreatureOrGO.spell))
			return false;
		break;
	case SMART_ACTION_REMOVEAURASFROMSPELL:
		if (!IsSpellValid(e, e.action.removeAura.spell))
			return false;
		break;
	case SMART_ACTION_RANDOM_PHASE: {
		if (e.action.randomPhase.phase1 >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhase.phase2 >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhase.phase3 >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhase.phase4 >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhase.phase5 >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhase.phase6 >= SMART_EVENT_PHASE_MAX) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u attempts to set invalid phase, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType());
			return false;
		}
	}
		break;
	case SMART_ACTION_RANDOM_PHASE_RANGE: //PhaseMin, PhaseMax
	{
		if (e.action.randomPhaseRange.phaseMin >= SMART_EVENT_PHASE_MAX
				|| e.action.randomPhaseRange.phaseMax >= SMART_EVENT_PHASE_MAX) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u attempts to set invalid phase, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType());
			return false;
		}
		if (!IsMinMaxValid(e, e.action.randomPhaseRange.phaseMin,
				e.action.randomPhaseRange.phaseMax))
			return false;
		break;
	}
	case SMART_ACTION_SUMMON_CREATURE:
		if (!IsCreatureValid(e, e.action.summonCreature.creature))
			return false;
		if (e.action.summonCreature.type > TEMPSUMMON_MANUAL_DESPAWN) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses incorrect TempSummonType %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.summonCreature.type);
			return false;
		}
		break;
	case SMART_ACTION_CALL_KILLEDMONSTER:
		if (!IsCreatureValid(e, e.action.killedMonster.creature))
			return false;
		break;
	case SMART_ACTION_UPDATE_TEMPLATE:
		if (e.action.updateTemplate.creature
				&& !IsCreatureValid(e, e.action.updateTemplate.creature))
			return false;
		break;
	case SMART_ACTION_SET_SHEATH:
		if (e.action.setSheath.sheath
				&& e.action.setSheath.sheath >= MAX_SHEATH_STATE) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses incorrect Sheath state %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.setSheath.sheath);
			return false;
		}
		break;
	case SMART_ACTION_SET_REACT_STATE: {
		if (e.action.react.state > REACT_AGGRESSIVE) {
			sLog->outErrorDb(
					"SmartAIMgr: Creature %d Event %u Action %u uses invalid React State %u, skipped.",
					e.entryOrGuid, e.event_id, e.GetActionType(),
					e.action.react.state);
			return false;
		}
		break;
	}
	case SMART_ACTION_SUMMON_GO:
		if (!IsGameObjectValid(e, e.action.summonGO.entry))
			return false;
		break;
	case SMART_ACTION_ADD_ITEM:
	case SMART_ACTION_REMOVE_ITEM:
		if (!IsItemValid(e, e.action.item.entry))
			return false;
		if (!NotNULL(e, e.action.item.count))
			return false;
		break;
	case SMART_ACTION_TELEPORT:
		if (!sMapStore.LookupEntry(e.action.teleport.mapID)) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Map entry %u, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), e.action.teleport.mapID);
			return false;
		}
		break;
	case SMART_ACTION_INSTALL_AI_TEMPLATE:
		if (e.action.installTtemplate.id >= SMARTAI_TEMPLATE_END) {
			sLog->outErrorDb(
					"SmartAIMgr: Creature %d Event %u Action %u uses non-existent AI template id %u, skipped.",
					e.entryOrGuid, e.event_id, e.GetActionType(),
					e.action.installTtemplate.id);
			return false;
		}
		break;
	case SMART_ACTION_WP_STOP:
		if (e.action.wpStop.quest && !IsQuestValid(e, e.action.wpStop.quest))
			return false;
		break;
	case SMART_ACTION_WP_START: {
		if (!sSmartWaypointMgr->GetPath(e.action.wpStart.pathID)) {
			sLog->outErrorDb(
					"SmartAIMgr: Creature %d Event %u Action %u uses non-existent WaypointPath id %u, skipped.",
					e.entryOrGuid, e.event_id, e.GetActionType(),
					e.action.wpStart.pathID);
			return false;
		}
		if (e.action.wpStart.quest && !IsQuestValid(e, e.action.wpStart.quest))
			return false;
		if (e.action.wpStart.reactState > REACT_AGGRESSIVE) {
			sLog->outErrorDb(
					"SmartAIMgr: Creature %d Event %u Action %u uses invalid React State %u, skipped.",
					e.entryOrGuid, e.event_id, e.GetActionType(),
					e.action.wpStart.reactState);
			return false;
		}
		break;
	}
	case SMART_ACTION_CREATE_TIMED_EVENT: {
		if (!IsMinMaxValid(e, e.action.timeEvent.min, e.action.timeEvent.max))
			return false;
		if (!IsMinMaxValid(e, e.action.timeEvent.repeatMin,
				e.action.timeEvent.repeatMax))
			return false;
		break;
	}
	case SMART_ACTION_FOLLOW:
	case SMART_ACTION_SET_ORIENTATION:
	case SMART_ACTION_STORE_TARGET_LIST:
	case SMART_ACTION_EVADE:
	case SMART_ACTION_FLEE_FOR_ASSIST:
	case SMART_ACTION_DIE:
	case SMART_ACTION_SET_IN_COMBAT_WITH_ZONE:
	case SMART_ACTION_SET_ACTIVE:
	case SMART_ACTION_STORE_VARIABLE_DECIMAL:
	case SMART_ACTION_WP_RESUME:
	case SMART_ACTION_KILL_UNIT:
	case SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL:
	case SMART_ACTION_RESET_GOBJECT:
	case SMART_ACTION_ATTACK_START:
	case SMART_ACTION_THREAT_ALL_PCT:
	case SMART_ACTION_THREAT_SINGLE_PCT:
	case SMART_ACTION_SET_INST_DATA:
	case SMART_ACTION_SET_INST_DATA64:
	case SMART_ACTION_AUTO_ATTACK:
	case SMART_ACTION_ALLOW_COMBAT_MOVEMENT:
	case SMART_ACTION_CALL_FOR_HELP:
	case SMART_ACTION_SET_DATA:
	case SMART_ACTION_MOVE_FORWARD:
	case SMART_ACTION_SET_VISIBILITY:
	case SMART_ACTION_WP_PAUSE:
	case SMART_ACTION_SET_FLY:
	case SMART_ACTION_SET_RUN:
	case SMART_ACTION_SET_SWIMM:
	case SMART_ACTION_FORCE_DESPAWN:
	case SMART_ACTION_SET_INGAME_PHASE_MASK:
	case SMART_ACTION_SET_UNIT_FLAG:
	case SMART_ACTION_REMOVE_UNIT_FLAG:
	case SMART_ACTION_PLAYMOVIE:
	case SMART_ACTION_MOVE_TO_POS:
	case SMART_ACTION_RESPAWN_TARGET:
	case SMART_ACTION_CLOSE_GOSSIP:
	case SMART_ACTION_EQUIP:
	case SMART_ACTION_TRIGGER_TIMED_EVENT:
	case SMART_ACTION_REMOVE_TIMED_EVENT:
	case SMART_ACTION_OVERRIDE_SCRIPT_BASE_OBJECT:
	case SMART_ACTION_RESET_SCRIPT_BASE_OBJECT:
	case SMART_ACTION_ACTIVATE_GOBJECT:
	case SMART_ACTION_CALL_SCRIPT_RESET:
	case SMART_ACTION_ENTER_VEHICLE:
	case SMART_ACTION_NONE:
	case SMART_ACTION_CALL_TIMED_ACTIONLIST:
	case SMART_ACTION_SET_NPC_FLAG:
	case SMART_ACTION_ADD_NPC_FLAG:
	case SMART_ACTION_REMOVE_NPC_FLAG:
	case SMART_ACTION_TALK:
	case SMART_ACTION_SIMPLE_TALK:
	case SMART_ACTION_CROSS_CAST:
	case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST:
	case SMART_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST:
	case SMART_ACTION_RANDOM_MOVE:
	case SMART_ACTION_SET_UNIT_FIELD_BYTES_1:
	case SMART_ACTION_REMOVE_UNIT_FIELD_BYTES_1:
	case SMART_ACTION_INTERRUPT_SPELL:
	case SMART_ACTION_SEND_GO_CUSTOM_ANIM:
	case SMART_ACTION_SET_DYNAMIC_FLAG:
	case SMART_ACTION_ADD_DYNAMIC_FLAG:
	case SMART_ACTION_REMOVE_DYNAMIC_FLAG:
		break;
	default:
		sLog->outErrorDb(
				"SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry %d SourceType %u Event %u, skipped.",
				e.GetActionType(), e.GetEventType(), e.entryOrGuid,
				e.GetScriptType(), e.event_id);
		return false;
	}

	return true;
}

bool SmartAIMgr::IsTextValid(SmartScriptHolder e, uint32 id) {
	bool error = false;
	uint32 entry = 0;
	if (e.entryOrGuid >= 0)
		entry = uint32(e.entryOrGuid);
	else {
		entry = uint32(abs(e.entryOrGuid));
		CreatureData const* data = sObjectMgr->GetCreatureData(entry);
		if (!data) {
			sLog->outErrorDb(
					"SmartAIMgr: Entry %d SourceType %u Event %u Action %u using non-existent Creature guid %d, skipped.",
					e.entryOrGuid, e.GetScriptType(), e.event_id,
					e.GetActionType(), entry);
			return false;
		} else
			entry = data->id;
	}
	if (!entry || !sCreatureTextMgr->TextExist(entry, uint8(id)))
		error = true;
	if (error) {
		sLog->outErrorDb(
				"SmartAIMgr: Entry %d SourceType %u Event %u Action %u using non-existent Text id %d, skipped.",
				e.entryOrGuid, e.GetScriptType(), e.source_type,
				e.GetActionType(), id);
		return false;
	}
	return true;
}
